package com.ylz.sjzx.sio.sdk.client.core;

import com.corundumstudio.socketio.SocketIOServer;
import com.ylz.sjzx.sio.common.core.constant.IMConstant;
import com.ylz.sjzx.sio.common.core.context.ApplicationUtils;
import com.ylz.sjzx.sio.common.core.core.container.ContainerManager;
import com.ylz.sjzx.sio.common.core.core.container.common.BaseScanner;
import com.ylz.sjzx.sio.sdk.client.core.ws.FinalChannelEventHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.handler.timeout.IdleStateHandler;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.TimeUnit;

@Slf4j
public class IMNioService {
    private static final Logger LOGGER = LoggerFactory.getLogger(IMNioService.class);
    private final Integer webPort;
    private final Integer socketIoPort;
    private EventLoopGroup webBossGroup;
    private EventLoopGroup webWorkerGroup;

    private IMNioService(Builder builder) {
        this.webPort = builder.webPort;
        this.socketIoPort = builder.socketIoPort;
    }

    public void bind() {
        //容器初始化
        ContainerManager.init();
        //ws 初始化
        if (this.webPort != null) {
            this.bindWebPort();
        }
        //socketIo 初始化
        if (this.socketIoPort != null) {
            this.bindSocketPort();
        }
        BaseScanner.destroyFilesCache();

    }

    private void bindSocketPort() {
        ApplicationUtils.getBean(SocketIOServer.class).start();
    }

    private void bindWebPort() {
        this.webBossGroup = new NioEventLoopGroup(5);
        this.webWorkerGroup = new NioEventLoopGroup(200);
        ServerBootstrap bootstrap = this.createServerBootstrap(this.webBossGroup, this.webWorkerGroup);
        bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {

            @Override
            public void initChannel(SocketChannel ch) {
                ch.pipeline().addLast(new HttpRequestDecoder());
                ch.pipeline().addLast(new ChunkedWriteHandler());
                // 将HttpMessage和HttpContents聚合到一个完成的 FullHttpRequest或FullHttpResponse中,具体是FullHttpRequest对象还是FullHttpResponse对象取决于是请求还是响应
                // 需要放到HttpRequestDecoder这个处理器后面
                ch.pipeline().addLast(new HttpObjectAggregator(65536));
                ch.pipeline().addLast(new HttpResponseEncoder());
                //ch.pipeline().addLast(new LoggingHandler(LogLevel.INFO));
                ch.pipeline().addLast(
                        new IdleStateHandler(IMNioBase.READ_IDLE_TIME, IMNioBase.WRITE_IDLE_TIME, 0,
                                TimeUnit.SECONDS));

                // webSocket 数据压缩扩展 start，当添加这个的时候WebSocketServerProtocolHandler的第三个参数需要设置成true
                ch.pipeline().addLast(new WebSocketServerCompressionHandler());
                ch.pipeline().addLast(new FinalChannelEventHandler());
                ch.pipeline().addLast(
                        new WebSocketServerProtocolHandler(IMConstant.DEFAULT_WEB_SOCKET_LINK, null, true));

            }

        });

        ChannelFuture channelFuture = bootstrap.bind(this.webPort).syncUninterruptibly();
        channelFuture.channel().newSucceededFuture().addListener(future -> {
            String logBanner = "\n\n" +
                    "* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\n" +
                    "*                                                                                   *\n" +
                    "*                                                                                   *\n" +
                    "*                   Websocket Server started on port {}.                         *\n" +
                    "*                                                                                   *\n" +
                    "*                                                                                   *\n" +
                    "* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\n";
            LOGGER.info(logBanner, this.webPort);
        });
        channelFuture.channel().closeFuture()
                .addListener(future -> this.destroy(this.webBossGroup, this.webWorkerGroup));
    }

    /**
     * 关闭长连接服务
     */
    public void destroy() {
        IMNioBase.isGlobalRun = false;
        log.info("全局的线程正在停止运行");
        this.destroy(this.webBossGroup, this.webWorkerGroup);
    }

    public void destroy(EventLoopGroup bossGroup, EventLoopGroup workerGroup) {
        if (bossGroup != null && !bossGroup.isShuttingDown() && !bossGroup.isShutdown()) {
            try {
                bossGroup.shutdownGracefully();
            } catch (Exception ignore) {
            }
        }

        if (workerGroup != null && !workerGroup.isShuttingDown() && !workerGroup.isShutdown()) {
            try {
                workerGroup.shutdownGracefully();
            } catch (Exception ignore) {
            }
        }
    }

    private ServerBootstrap createServerBootstrap(EventLoopGroup bossGroup, EventLoopGroup workerGroup) {
        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(bossGroup, workerGroup);
        bootstrap.childOption(ChannelOption.TCP_NODELAY, true);
        bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
        bootstrap.channel(NioServerSocketChannel.class);
        return bootstrap;
    }

    public static class Builder {

        private Integer webPort;

        private Integer socketIoPort;

        public Builder setSocketIoPort(Integer socketIoPort) {
            this.socketIoPort = socketIoPort;
            return this;
        }

        public Builder setWebsocketPort(Integer port) {
            this.webPort = port;
            return this;
        }

        public IMNioService build() {
            return new IMNioService(this);
        }

    }

}
